Objetivo: classificar índices, estimar quantos blocos uma busca lê e, principalmente, montar à mão uma árvore B+: calcular quantas entradas cabem no nó, inserir com split, remover com empréstimo/unsplit e desenhar o arquivo do índice.
A ideia em uma frase. Pense no índice remissivo no fim de um livro. Em vez de ler o livro inteiro para achar um assunto, você procura o assunto numa lista em ordem alfabética, e ela diz a página. O índice do banco é igual: uma lista ordenada de <chave, rid>, em que o rid diz em que bloco do arquivo de dados está a linha. A árvore B+ é essa lista organizada em níveis: a raiz diz em que parte procurar, e as folhas guardam as chaves com os rids. Cada nó é um bloco, então descer um nível é ler 1 bloco. Com 2 ou 3 níveis, o banco acha qualquer chave lendo 2 ou 3 blocos, em vez do arquivo inteiro.
O que a questão pede. O desenho da árvore B+ depois de uma sequência de INSERT e DELETE, com os nós numerados, as entradas e as setas, e o desenho do arquivo do índice (que bloco é cada nó).
Palavras que vão aparecer:
id).Onde este arquivo se encaixa. É o 2º de 3. Os rids das folhas vêm do arquivo de dados ("Banco de Dados: Organização de Arquivos"). O número de níveis () e de folhas () do índice entram nas contas de "Banco de Dados: Custo de Consultas".
Se você está perdido, leia nesta ordem:
As seções 2 e 3 são teoria de consulta; dá para deixá-las por último.
<chave de busca, rowId>. É como o índice remissivo de um livro.CREATE [UNIQUE] INDEX nome ON tabela(atributo [, atributo ...]); varre a tabela, pega os valores e rowIds e grava o índice num arquivo bem menor que o de dados.INSERT, DELETE, UPDATE, ALTER/DROP TABLE.| Índice | Arquivo ordenado pelo atributo? | Atributo único? | Denso ou esparso | Como busca |
|---|---|---|---|---|
| Primário | Sim | Sim | Esparso: 1 entrada por bloco (o 1º registro do bloco é a âncora) | Acha o bloco (maior chave ≤ K) e procura dentro dele. |
| Clustering | Sim | Não | Esparso: 1 entrada por valor distinto (ou âncora por bloco) | Acha o 1º bloco do valor e lê até o valor acabar. |
| Secundário único | Não | Sim | Denso: 1 entrada por registro | Chave → rowId → registro. |
| Secundário não único | Não | Não | Denso (chave repetida, um rowId cada) ou chave + lista de rowIds (um nível a mais de indireção) | Igual, com 1 acesso por registro encontrado. |
<chave, rowId> com busca binária. Inserção e remoção exigem reorganizá-lo.CREATE UNIQUE CLUSTERED INDEX).Se está começando, pule para a seção 4 e volte aqui depois. Esta seção compara números de acessos e usa os termos da seção 2.
Exemplo clássico do livro de Elmasri & Navathe. O arquivo tem registros de B, em blocos de B (unspanned). Uma entrada de índice tem chave de 9 B + ponteiro de 6 B = 15 B.
é o fan-out: quantas entradas de índice cabem num bloco.
| Cenário | Conta | Acessos |
|---|---|---|
| Arquivo ordenado pelo atributo, busca binária no arquivo | 12 | |
| Índice primário mononível (esparso: 3 000 entradas, uma por bloco) | , então | 7 |
| Arquivo não ordenado, atributo com repetidos em média: busca sequencial | 3 000 | |
| Índice secundário mononível denso (30 000 entradas) | , então | 14 |
| Índice secundário multinível | 442 → → : níveis, então | 8 |
Esta é a árvore final do exemplo resolvido (seção 12). Cada folha guarda chave rid, e a última linha é a lista encadeada das folhas:
n3 ( CR3 | GN2 | NB2 | W10 )
┌───────────┬───────────┼───────────┬───────────┐
▼ ▼ ▼ ▼ ▼
n1 n7 n5 n6 n4
AMP 0202 GE2 0302 MSP 0105 RSR 0204 WFL 0203
AZA 0102 GN2 0205 NB2 0103 W10 0101 WRF 0104
CR3 0303 WRP 0201
n1 ───────► n7 ───────► n5 ───────► n6 ───────► n4 ──► NULL
Buscar RSR (busca pontual). Na raiz, compare RSR com os separadores da esquerda para a direita e desça pelo primeiro cuja chave é ≥ RSR:
RSR 0204: a linha está no bloco 02, slot 04 do arquivo de dados.Por que "≤ vai para a esquerda". O separador é a maior chave da subárvore da esquerda: NB2 é a última chave de n5. Para buscar o próprio NB2, compare NB2 ≤ NB2: verdadeiro, então desça à esquerda e chegue no n5. Por isso, no split, é a maior chave da metade esquerda que sobe.
Buscar de GE2 até NB2 (busca por intervalo). Desça até a folha do GE2 (n7) como na busca pontual. Depois siga a lista encadeada: n7 (GE2, GN2) → n5 (MSP, NB2), e pare quando passar de NB2. Não precisa voltar à raiz, e é para isso que as folhas são encadeadas.
E quando insere ou remove? A árvore precisa continuar assim: chaves em ordem, todas as folhas no mesmo nível e cada nó com pelo menos metade da capacidade. O split (nó cheio demais) e o unsplit (nó vazio demais) são os consertos que mantêm isso. As seções 7 e 8 mostram como fazer cada um.
Cada nó ocupa um bloco de bytes. A chave tem bytes, o rowId tem bytes e o ponteiro de árvore tem bytes. Sobra sempre um ponteiro: na folha é o da próxima folha, e no nó interno é o ponteiro mais à direita.
| Chave (, , ) | Folha | Nó interno | |
|---|---|---|---|
CHAR(3), 2 B por caractere |
6 B | chaves, 6 ponteiros | |
INT de 8 B |
8 B | chaves, 6 ponteiros | |
CHAR(10), 2 B por caractere |
20 B | chaves, 5 ponteiros |
1 < F.'10' < '9'. INT compara como número.AMP < AZA < CR3 < GE2 < GN2 < MSP < NB2
< RSR < RV6 < RV7 < W10 < WFL < WRF < WRP
🔑 Na folha a chave é copiada e fica nos dois lugares. No nó interno a chave se move.
# | chave e rid | folha | o que acontece | raiz depois. O passo a passo vale nota parcial.NULL.Livro de Elmasri & Navathe: árvore B+ de ordem 3 (: nó interno com até 3 ponteiros e 2 chaves; entradas por folha). Inserções: 8, 5, 1, 7, 3, 12, 9, 6. Os nós são numerados na ordem de criação (). É o exemplo que mostra o split de nó interno e o da raiz.
INSERT 8 n1 [8]
INSERT 5 n1 [5 8] ← folha cheia
INSERT 1 [1 5 8] estoura → n1 [1 5] | n2 [8], sobe 5 → nova raiz n3
n3 ( 5 )
/ \
n1 [1 5] → n2 [8]
INSERT 7 n2 [7 8]
INSERT 3 3 ≤ 5 → [1 3 5] estoura → n1 [1 3] | n4 [5], sobe 3
n3 ( 3 | 5 )
/ | \
n1 [1 3] → n4 [5] → n2 [7 8]
INSERT 12 [7 8 12] estoura → n2 [7 8] | n5 [12], sobe 8
n3 ( 3 | 5 | 8 ) estoura (máx. 2 chaves): 5 SOBE e sai,
n3 ( 3 ) | n6 ( 8 ), nova raiz n7
n7 ( 5 )
/ \
n3 ( 3 ) n6 ( 8 )
/ \ / \
n1 [1 3] → n4 [5] → n2 [7 8] → n5 [12]
INSERT 9 n5 [9 12]
INSERT 6 6 > 5, 6 ≤ 8 → [6 7 8] estoura → n2 [6 7] | n8 [8], sobe 7
n6 ( 7 | 8 ): cabe
Árvore final:
Premissas: blocos de 256 B; chave
id CHAR(3)com 2 B por caractere (6 B); rid de 64 B; ponteiro de árvore de 32 B; valores ≤ a chave ficam à esquerda; em split/unsplit, o nó esquerdo nunca fica com menos entradas que o direito; dígitos < letras; folhas em lista simplesmente encadeada; o header guarda o nº de blocos, a raiz e a folha mais à esquerda. O índice é o da chave primária deproduct, e os rids vêm do arquivo de dados (ver o cheat sheet "Banco de Dados: Organização de Arquivos").Instruções: 13
INSERT(W10, AZA, NB2, WRF, MSP, WRP, RV6, WFL, RSR, GN2, RV7, GE2, CR3), depoisDELETEde RV6 e RV7, depoisINSERTde AMP (rid 0202).
Passo 1. Capacidade. Folha: entradas. Interno: chaves e 6 ponteiros. Uma folha entra em underflow com 1 entrada.
Passo 2. Ordem das chaves. AMP < AZA < CR3 < GE2 < GN2 < MSP < NB2 < RSR < RV6 < RV7 < W10 < WFL < WRF < WRP (a pegadinha é W10 < WFL).
Passo 3. As 13 inserções. 4 entradas numa folha de 3 dividem 2 e 2.
| # | INSERT | Folha | O que acontece | Raiz (n3) |
|---|---|---|---|---|
| 1 | W10 0101 | 1 | n1 nasce: folha e raiz ao mesmo tempo | — |
| 2 | AZA 0102 | 1 | [AZA W10] | — |
| 3 | NB2 0103 | 1 | [AZA NB2 W10], cheia | — |
| 4 | WRF 0104 | 1 | SPLIT: n1 [AZA NB2], n2 [W10 WRF]; sobe NB2; nasce a raiz n3 | NB2 |
| 5 | MSP 0105 | 1 | [AZA MSP NB2] | NB2 |
| 6 | WRP 0201 | 2 | [W10 WRF WRP] | NB2 |
| 7 | RV6 0202 | 2 | SPLIT: n2 [RV6 W10], n4 [WRF WRP]; sobe W10 | NB2 W10 |
| 8 | WFL 0203 | 4 | [WFL WRF WRP] | NB2 W10 |
| 9 | RSR 0204 | 2 | [RSR RV6 W10] | NB2 W10 |
| 10 | GN2 0205 | 1 | SPLIT: n1 [AZA GN2], n5 [MSP NB2]; sobe GN2 | GN2 NB2 W10 |
| 11 | RV7 0301 | 2 | SPLIT: n2 [RSR RV6], n6 [RV7 W10]; sobe RV6 | GN2 NB2 RV6 W10 |
| 12 | GE2 0302 | 1 | [AZA GE2 GN2] | GN2 NB2 RV6 W10 |
| 13 | CR3 0303 | 1 | SPLIT: n1 [AZA CR3], n7 [GE2 GN2]; sobe CR3 | CR3 GN2 NB2 RV6 W10 |
Passo 4. Antes das remoções (raiz com 5 chaves, cheia; folhas encadeadas n1 → n7 → n5 → n2 → n6 → n4):
n3 ( CR3 | GN2 | NB2 | RV6 | W10 )
┌──────────┬──────────┬────┴─────┬──────────┬──────────┐
▼ ▼ ▼ ▼ ▼ ▼
n1 n7 n5 n2 n6 n4
AZA|0102 GE2|0302 MSP|0105 RSR|0204 RV7|0301 WFL|0203
CR3|0303 GN2|0205 NB2|0103 RV6|0202 W10|0101 WRF|0104
WRP|0201
Passo 5. Remoções.
juntar com o ESQUERDO: n5 [MSP NB2 RSR] | n6 [RV7 W10] 3 ≥ 2 respeita
juntar com o DIREITO: n5 [MSP NB2] | n2 [RSR RV7 W10] 2 < 3 VIOLA
O n5 absorve o n2, que é liberado. A raiz perde o separador entre eles (NB2) e o ponteiro para o n2. RV6 continua na raiz, porque separador não se apaga, e ela ainda separa certo (RSR ≤ RV6 < RV7). Raiz: ( CR3 | GN2 | RV6 | W10 ).
( CR3 | GN2 | NB2 | W10 ).Passo 6. INSERT AMP (0202). AMP ≤ CR3 → n1 [AMP AZA CR3]: fica cheia, sem split.
Resposta: árvore final.
| Ponteiro da raiz | Faixa | Folha | Chaves |
|---|---|---|---|
| 1º | k ≤ CR3 | n1 | AMP, AZA, CR3 |
| 2º | CR3 < k ≤ GN2 | n7 | GE2, GN2 |
| 3º | GN2 < k ≤ NB2 | n5 | MSP, NB2 |
| 4º | NB2 < k ≤ W10 | n6 | RSR, W10 |
| 5º | k > W10 | n4 | WFL, WRF, WRP |
São 12 chaves, as mesmas 12 tuplas do arquivo de dados. Foram criados 7 nós, e um foi liberado (n2).
Resposta: arquivo do índice.
header: nº de blocos = 7 | raiz = bloco 3 | folha mais à esquerda = bloco 1
bloco 1 2 3 4 5 6 7
┌───────┬───────┬───────┬───────┬───────┬───────┬───────┐
│ folha │ LIVRE │ RAIZ │ folha │ folha │ folha │ folha │
└───────┴───────┴───────┴───────┴───────┴───────┴───────┘
▲ liberado pelo unsplit do DELETE RV6
ordem lógica das folhas: 1 → 7 → 5 → 6 → 4 → NULL
'10' < '9' em texto.| Se o enunciado pedir… | Faça |
|---|---|
Índice de outro atributo (ex.: UNIQUE (name) em CHAR(10)) |
Recalcule a capacidade com o novo : 2 por folha e 4 chaves por nó interno no exemplo. Ordene pelas novas chaves e use os mesmos rids do arquivo de dados. |
Índice de chave INT |
B; compare como número. |
| Muitas inserções seguidas | Espere o split do nó interno e o split da raiz (exemplo clássico, seção 11). |
| Remoção que esvazia o pai | Propague: empréstimo/junção entre nós internos (o separador do pai desce) e, no limite, a raiz encolhe. |
| "≥ vai para a esquerda" ou "esquerda ≤ direita" | Inverta a regra de desempate e diga isso na resposta. |
| Árvore B (não B+) | As chaves dos nós internos não se repetem nas folhas, e cada chave aparece uma vez com o seu rowId. |
| Índice secundário não único | A chave se repete, uma entrada por rowId (ou chave + lista de rowIds). |